package com.bckj.fastboot.flow.process;

import com.bckj.fastboot.core.util.ExecutionContext;
import com.bckj.fastboot.flow.model.*;
import com.bckj.fastboot.flow.process.consumer.BpmnProcessConsumer;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;

@Slf4j
public class BpmnExecVisitor implements BpmnVisitor {

    private final BpmnProcess bpmnProcess;
    private final ExecutionContext context;
    private final Executor executor;
    private final BpmnProcessConsumer bpmnProcessConsumer;
    private final BpmnElResolver bpmnElResolver = new BpmnElResolver();

    public BpmnExecVisitor(Executor executor, BpmnProcess bpmnProcess, ExecutionContext context, BpmnProcessConsumer bpmnProcessConsumer) {
        this.bpmnProcess = bpmnProcess;
        this.context = context;
        this.executor = executor;
        this.bpmnProcessConsumer = bpmnProcessConsumer;
    }

    @Override
    public void visit(BpmnStartEvent startEvent) {
        consumeStartEvent(startEvent);
        outgoingFlow(startEvent);
    }

    @Override
    public void visit(BpmnServiceTask serviceTask) {
        consumeServiceTask(serviceTask);
        outgoingFlow(serviceTask);
    }

    @Override
    public void visit(BpmnSequenceFlow sequenceFlow) {
        consumeSequenceFlow(sequenceFlow);
        //获取target
        BpmnElement targetElement = bpmnProcess.getElementById(sequenceFlow.getTargetRef());
        if (targetElement instanceof BpmnParallelGateway parallelGateway) {
            if (!parallelGateway.isStart()) {
                //并行结束节点不在此调用，而是由开始节点调用
                return;
            }
        }
        if (targetElement != null) {
            targetElement.accept(this);
        }
    }

    @Override
    public void visit(BpmnEndEvent endEvent) {
        consumeEndEvent(endEvent);
    }

    @Override
    public void visit(BpmnExclusiveGateway exclusiveGateway) {
        consumeExclusiveGateway(exclusiveGateway);
        for (BpmnSequenceFlow flow : exclusiveGateway.getOutgoingFlows()) {
            if (flow.getExpression() != null && !flow.getExpression().isBlank()) {
                // 计算表达式
                boolean result = Boolean.TRUE.equals(bpmnElResolver.getValue(context, flow.getExpression(), Boolean.class));
                log.debug("Expression result: " + result);
                if (result) {
                    visit(flow);
                    break;
                }
            }
        }
    }

    @Override
    public void visit(BpmnParallelGateway parallelGateway) {
        consumeParallelGateway(parallelGateway);
        if (parallelGateway.isStart()) {
            //并行开始逻辑
            CountDownLatch latch = new CountDownLatch(parallelGateway.getOutgoingFlows().size());
            for (BpmnSequenceFlow flow : parallelGateway.getOutgoingFlows()) {
                executor.execute(() -> {
                    log.debug("Parallel gateway running... id=" + parallelGateway.getId() + " threadName=" + Thread.currentThread().getName());
                    visit(flow);
                    latch.countDown();
                    log.debug("Parallel gateway finish id=" + parallelGateway.getId() + " threadName=" + Thread.currentThread().getName());
                });
            }
            try {
                latch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            BpmnParallelGateway endParallelGateway = (BpmnParallelGateway) bpmnProcess.getElementById(parallelGateway.getEndId());
            endParallelGateway.accept(this);
        } else {
            //并行结束逻辑
            log.debug("Parallel gateway end id=" + parallelGateway.getId() + " threadName=" + Thread.currentThread().getName());
            List<BpmnSequenceFlow> outgoingFlows = parallelGateway.getOutgoingFlows();
            //只出一个
            BpmnSequenceFlow out = outgoingFlows.get(0);
            visit(out);
        }
    }

    @Override
    public void visit(BpmnCallActivity element) {
        log.debug("Call activity: " + element.getId());
        bpmnProcessConsumer.consume(context, element);
    }

    private void outgoingFlow(BpmnElement element) {
        log.debug("SequenceFlow " + element.getType() + ": " + element.getId());
        for (BpmnSequenceFlow flow : element.getOutgoingFlows()) {
            visit(flow);
        }
    }

    private void consumeStartEvent(BpmnStartEvent element) {
        log.debug("Start event: " + element.getId());
        bpmnProcessConsumer.consume(context, element);
    }

    private void consumeServiceTask(BpmnServiceTask element) {
        log.debug("Executing service task start >> " + element.getId() + " " + Thread.currentThread().getName() + " at=" + LocalDateTime.now());
        Instant startTime = Instant.now();
        bpmnProcessConsumer.consume(context, element);
        log.debug("Executing service task end   << " + element.getId() + " millis=" + Duration.between(startTime, Instant.now()).toMillis() + "ms");
    }

    private void consumeSequenceFlow(BpmnSequenceFlow element) {
        log.debug("SequenceFlow: " + element.getId());
        bpmnProcessConsumer.consume(context, element);
    }

    private void consumeEndEvent(BpmnEndEvent element) {
        log.debug("End event: " + element.getId());
        bpmnProcessConsumer.consume(context, element);
    }

    private void consumeExclusiveGateway(BpmnExclusiveGateway element) {
        log.debug("Exclusive gateway: " + element.getId());
        bpmnProcessConsumer.consume(context, element);
    }

    private void consumeParallelGateway(BpmnParallelGateway element) {
        log.debug("Parallel gateway: " + element.getId());
        bpmnProcessConsumer.consume(context, element);
    }
}
